#include "afw/afw.h"
#include "afw/afwfile.h"

#include <stdio.h>

using namespace one_rag;

class IniData : public impl::Buffer<char>{
  public:
  String ReadValue(String strSection,String strName){
    String ret = "";
    return ret;
  }
};

class XmlElement {
  char const* _data;
  public:
    XmlElement(char const* _data);
    //XmlNode(const XmlNode& rhs);
    //XmlNode& operator=(const XmlNode& rhs);

    String Name() const;
    String Attribute(String attrName) const;
    int DataLength()const;
    const char* DataPtr()const;
    String Data()const;
    operator bool()const;

    XmlElement NextElement()const;
    XmlElement ChildElement()const;

    private:
      static const char* NextTag(const char* startTag, int& depth);// starting at a <, finds the next <, and modifies depth by -1, 0 or 1
      static int NextAttribute(const char* scan); // attributes are ' ' seperated name=value pairs.

      // http://www.w3.org/TR/xml11
      static int is_S(int ch);
      static int is_NameStartChar(int ch);
      static int is_NameChar(int ch);
      static int is_Eq(char const* str);
      static int is_Name(const char* str);
      static int is_AttrValue(const char* str);
      static int is_Attribute(const char* str);
      static char parse_Reference(const char* str, int & i);
};


inline int XmlElement::is_S(int ch){
  return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
}

inline int XmlElement::is_NameStartChar(int ch){
  return ch == ':' || ( ch >='A' && ch <= 'Z') || ch == '_' || (ch >= 'a' && ch <= 'z') ;
  // also [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
}

inline int XmlElement::is_NameChar(int ch){
  return is_NameStartChar(ch) || ch == '-' || ch == '.' || (ch >= '0' && ch <= '9');
  // also : #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
}

inline int XmlElement::is_Eq(char const* str){
  int i=0;
  char ch;;
  for(;is_S(ch=str[i]);i++);
  if(ch != '=')
    return 0;
  while(is_S(ch=str[++i]));
  return i;
}

inline int XmlElement::is_Name(const char* str){
  int i=0;
  if(is_NameStartChar(str[i]))
  {
    while(is_NameChar(str[++i]));
  }
  return i;
}

inline int XmlElement::is_AttrValue(const char* str){
  char ch = str[0];
  int i=0;
  if(ch == '"'){
    while( (ch=str[++i]) != '"' && ch != '<' ){
    }
    if(ch=='\"')
      i++;
  }
  else if(ch == '\''){
    while( (ch=str[++i]) != '\'' && ch != '<' ){
    }
    if(ch=='\'')
      i++;
  }
  return i;
}

inline int XmlElement::is_Attribute(const char* str){
  int r;
  r=is_Name(str);
  r+=is_Eq(str+r);
  r+=is_AttrValue(str+r);
  return r;
}

inline char XmlElement::parse_Reference(const char* str, int&i){
  // assume str+i = '&'
  char ret = 0;
  char ch =  str[i];
  if(ch=='#'){ // character reference
    if( (ch = str[++i])== 'x'){ // parse as hex
      for(;(ch=str[++i])!=';';){
        ret *= 16;
        if(ch>='0' && ch <= '9')
          ret += ch-'0';
        else if(ch >= 'a' && ch <= 'f')
          ret += ch-'a'+10;
        else if(ch >= 'A' && ch <= 'F')
          ret += ch-'A'+10;
        else
          break;
      }
    }
    else { // parse as decimal
      for(;(ch=str[++i])!=';';){
        ret *= 10;
        if(ch>='0' && ch <= '9')
          ret += ch-'0';
        else
          break;
      }
    }
  } // entity ref
  else {
    ret = '?';
  }
  if(ch==';')
    i++;
  return ret;
}

XmlElement::XmlElement(char const* data):_data(data){
  if(_data){
    if(_data[0] == (char)0xEF && _data[1] == (char)0xBB && _data[2] == (char)0xBF){
      // data starts with a UTF-8 BOM. Skip it.
      _data+=3;
    }
  }
}

XmlElement::operator bool() const{
  return _data != 0;
}

String XmlElement::Name() const{
  String name;
  if(_data[0] == '<'){
    if(int len = is_Name(_data+1))
      name.Copy(_data+1,len);
  }
  return name;
}

// static
const char* XmlElement::NextTag(char const* data, int& level){
    int i=1;
    int ch = data[i];

      // scan off a tag.
      if(ch=='/')
        level--;
      else if(ch != '!' && ch != '?')
        level++;

      while(data[i] != '>')
        i++;

      if(data[i-1]=='/')
        level--;

      // now look for the next tag opener:
      for(;data[i]!='<';i++);

    return data+i;
}

int XmlElement::NextAttribute(const char* data){
  int i=0;
  char ch = data[i];
  while(ch != '>' && ch != ' '){
    if(ch == '"'){ // strings can contain "'s and >'s
      while( (ch = data[++i]) != '"')
      {
      }
    }
    ch = data[++i];
  }
  return i;
}

XmlElement XmlElement::NextElement() const {
  int level = 0;
  const char* scan=_data;
  do{
    scan=NextTag(scan,level);
  }
  while(level >0);

   if(scan[1]!= '/')
      return XmlElement(scan);

  return XmlElement(0);
}

XmlElement XmlElement::ChildElement() const {
    int level = 0;
    const char* scan = _data;

    scan = NextTag(scan,level);
    if(level == 1 && scan[1]!= '/')
      return XmlElement(scan);

  return XmlElement(0);
}

String XmlElement::Attribute(String attrName) const {
  char const* scan = _data;

  if(int i = is_Name(++scan))
  {
    for(;is_S(scan[i]);i++);

    while(scan[i] != '>')
    {
      if( (i = is_Name(scan+=i)) ){ // found an actual name
        int nameLen = i;
        if( (i += is_Eq(scan+i)) ){ // and there was a legit =
          if(!attrName.Compare(scan,nameLen)){
            String ret(256);
            char ch;
            if((ch=scan[i])=='"'){
              while(  (ch = scan[++i])!= '<' && ch != '"' ){
                if(ch=='&'){
                  ret+= parse_Reference(scan,i);
                }
                else
                  ret += ch;
              }
            }
            return ret;
          } // attrubute name is a match
          else{
            i=is_Attribute(scan+=i);
          }
        }// not a legit name=value
      }// found a name
      for(;is_S(scan[i]);i++);
    } // while next char isn't >
  } // tag has a name
  return String(0);
}



class XmlData : public impl::Buffer<char>{
  public:
    XmlElement GetRootNode(){
      return XmlElement(*this);
  }
};

IniData ReadFileAsIni(String fileName)
{
  IniData ini;
  File f;
  if(f.Open(fileName,create))
  {
    ini.Alloc(f.Size());
    f.Read(ini,ini.Size());
  }
  return ini;
}

XmlData ReadFileAsXml(String fileName)
{
  XmlData buf;
  File f;
  if(f.Open(fileName,create))
  {
    buf.Alloc(f.Size());
    f.Read(buf,buf.Size());
  }
  return buf;
}

void DumpElements(XmlElement node){

  String nextName = node.Name();
  if(nextName){
    printf("Element Name: %s\r\n",(const char*)nextName);

    XmlElement child = node.ChildElement();
    String strName = node.Attribute("name");
    String strDescription = node.Attribute("description");
    if(strName)
      printf("  name=%s\n",(char const*) strName);
    if(strDescription)
      printf("  desc=%s\n",(const char*)strDescription);
    if(child){
      printf("Dumping Children...\n");
      while(child){
        DumpElements(child);
        child = child.NextElement();
      }
      printf("done.\n");
    }
  }
}

int main()
{
  IniData ini = ReadFileAsIni(String("file.ini"));
  //String str = ini.ReadValue("section","name");

  XmlData xmlFile = ReadFileAsXml("Microsoft.MSXML2.manifest");

  XmlElement node = xmlFile.GetRootNode();
  String nodeName = node.Name();

  node = node.NextElement();
  DumpElements(node);

  return 0;
}
